/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  runNext,
  bitfield,
  // bitfields
  kObjectMode,
  kErrorEmitted,
  kAutoDestroy,
  kEmitClose,
  kDestroyed,
  kClosed,
  kCloseEmitted,
  kErrored,
  kConstructed,
  Stream,
} from './stream';
import buffer from '@ohos.buffer'

const kSync = 1 << 9;
const kFinalCalled = 1 << 10;
const kNeedDrain = 1 << 11;
const kEnding = 1 << 12;
const kFinished = 1 << 13;
const kDecodeStrings = 1 << 14;
const kWriting = 1 << 15;
const kBufferProcessing = 1 << 16;
const kPrefinished = 1 << 17;
const kAllBuffers = 1 << 18;
const kAllNoop = 1 << 19;
const kOnFinished = 1 << 20;
const kHasWritable = 1 << 21;
const kWritable = 1 << 22;
const kCorked = 1 << 23;
const kDefaultUTF8Encoding = 1 << 24;
const kWriteCb = 1 << 25;
const kExpectWriteCb = 1 << 26;
const kAfterWriteTickInfo = 1 << 27;
const kAfterWritePending = 1 << 28;
const kBuffered = 1 << 29;
const kEnded = 1 << 30;

type chunk = string | buffer.Buffer | Uint8Array | any;
type chunkWrap = {
  chunk: string | buffer.Buffer;
  encoding: string | null;
};

class WritableState {
  length: number;
  writecb?: Function;
  constructor() {
      this[bitfield] = 0;
      this.writecb = undefined;
  }
}

function nopWritev(chunks: chunkWrap[], callback: Function) {}

class Writable extends Stream {
  _writableState: WritableState;
  constructor() {
    super();
    this._writableState = new WritableState();
  }

  _writev = nopWritev;

  _write(chunk: chunk, encoding: string | null, callback: Function) {
    if (this._writev !== nopWritev) {
      this._writev([{chunk, encoding}], callback);
    } else {
      throw new ERR_METHOD_NOT_IMPLEMENTED('_write()');
    }
  }

  write(chunk: chunk, encoding?: string | null, callback?: Function) {
    if (encoding != null && typeof encoding === 'function') {
      callback = encoding;
      encoding = null;
    }
    return writeImpl(this, chunk, encoding, callback) === true;
  }
}

function writeImpl(stream: Writable, chunk: chunk, encoding?: string | null, callback?: Function): Error | boolean {
  if (chunk == null) {
    throw new Error();
  }

  const state = stream._writableState;
  if ((state[bitfield] & kObjectMode)) {
    if (!encoding) {
      encoding = (state[bitfield] & kDefaultUTF8Encoding) !== 0 ? 'utf8' : state.defaultEncoding;
    } else if (encoding !== 'buffer' && !buffer.isEncoding(encoding)) {
      throw new ERR_UNKNOWN_ENCODING(encoding);
    }

    if (typeof chunk === 'string') {
      if ((state[bitfield] & kDecodeStrings) !== 0) {
        chunk = buffer.from(chunk, encoding);
        encoding = 'buffer';
      }
    } else if (chunk instanceof buffer.Buffer) {
      encoding = 'buffer';
    } else {
      try {
        chunk = buffer.from(chunk);
        encoding = 'buffer';
      } catch (e) {
        throw new Error(e);
      }
    }
  }
  // may happen only in objectMode
  if (!encoding) {
    encoding = null;
  }

  let err;
  if ((state[bitfield] & kEnding) !== 0) {
    err = new ERR_STREAM_WRITE_AFTER_END();
  } else if ((state[bitfield] & kDestroyed) !== 0) {
    err = new ERR_STREAM_DESTROYED('write');
  }

  if (err) {
    if (callback !== undefined) {
      runNext(()=>callback(err));
    }
    errorOrDestroy(stream, err, true);
    return err;
  }

  state.pendingcb++;
  return writeOrBuffer(stream, chunk, encoding, callback);
}

function writeOrBuffer(stream: Writable, chunk: chunk, encoding: string | null, callback?: Function) {
  const state = stream._writableState;
  const len = (state[bitfield] & kObjectMode) !== 0 ? 1 : chunk.length;

  state.length += len;

  if ((state[bitfield] & (kWriting | kErrored | kCorked | kConstructed)) !== kConstructed) {
    if ((state[bitfield] & kBuffered) === 0) {
      state[bitfield] |= kBuffered;
      state[kBufferedValue] = [];
    }

    state[kBufferedValue].push({ chunk, encoding, callback });
    if ((state[bitfield] & kAllBuffers) !== 0 && encoding !== 'buffer') {
      state[bitfield] &= ~kAllBuffers;
    }
    if ((state[bitfield] & kAllNoop) !== 0 && callback !== nop) {
      state[bitfield] &= ~kAllNoop;
    }
  } else {
    state.writelen = len;
    state.writecb = callback;
    state[bitfield] |= kWriting | kSync | kExpectWriteCb;
    stream._write(chunk, encoding, state.onwrite);
    state[bitfield] &= ~kSync;
  }

  const ret = state.length < state.highWaterMark;

  if (!ret) {
    state[bitfield] |= kNeedDrain;
  }

  // Return false if errored or destroyed in order to break
  // any synchronous while(stream.write(data)) loops.
  return ret && (state[bitfield] & (kDestroyed | kErrored)) === 0;
}

export default class {
    Writable
};